Keygenme - JuLio
eL NeGRo [zip]
- Type de la protection
: nom / numéro de série
- Méthode utilisée
: reversing
- Outils utilisés
:
Dans ce tutoriel,
nous allons étudier comment transformer un programme en son propre keygener
quand vous avez un peu de mal à le keygener, ou si vous n'avez pas vraiment
le temps. On exécute le fichier exécutable, on clique sur le bouton 'Check'
et une fenêtre apparaît avec comme message 'Wrong Serial !'.
On va donc chercher toute relation avec ce message, comment apparaît-il,
etc... On ouvre alors le fichier exécutable avec IDA (je n'utilise pas
W32Dasm tout simplement parce qu'il ne fonctionne pas chez moi, et IDA
est très pratique, pour cela allez voir les tutz de Netix sur IDA).
On va dans le menu
'Search' puis 'text...', on entre 'Wrong
Serial !' dans le champ, puis 'OK'.
On arrive alors à cette ligne :
.data:004060AF aWrongSerial db 'Wrong
Serial !',0 ; DATA XREF: sub_4043A3+66|o
En jaune nous avons l'adresse
en mémoire dans le programme, ensuite le nom de la variable, le type de
la variable, puis la valeur affectée à la variable. En commentaire, IDA
nous indique dans quel endroit du programme cette variable est appelée.
On double-clique donc sur sub_4043A3+66, et on arrive ici :
.text:00404402
push 0 ...................;
uType
.text:00404404 push offset aError .......;
lpCaption
.text:00404409 push
offset aWrongSerial .;
lpText
.text:0040440E push [ebp+hWnd]
; hWnd
.text:00404411 call
MessageBoxA
On voit bien
ici l'appel à la MessageBox !
Nous avons en
paramètre de la MessageBox le type (ici juste le bouton OK), l'adresse
du texte de la barre de titre, l'adresse du texte de la boîte, et enfin
le handle du programme pour attacher la boîte au programme.
On a déjà une idée où poser un breakpoint. On retient donc l'adresse 00404409.
On exécute OllyDbg et on ouvre le crackme, ensuite on fait soit un clic-droit,
'Go to' et 'Expression', soit Ctrl+G, on entre l'adresse
et 'OK'. On voit clairement les 2 appels MessageBox selon le résultat
de la vérification.
On remonte légèrement, et voici ce qu'on observe :
004043F4 |. E8 51000000
..CALL Keygenm.0040444A
004043F9 |. E8 AB000000 ..CALL Keygenm.004044A9
004043FE |. 85C0 .........TEST
EAX,EAX
00404400 |. 75 16 ........JNZ
SHORT Keygenm.00404418
00404402 |. 6A 00 ........PUSH
0 ........................;
/Style =
MB_OK|MB_APPLMODAL
00404404 |. 68 BE604000 ..PUSH Keygenm.004060BE
.........;
|Title = "error"
00404409 |. 68 AF604000 ..PUSH Keygenm.004060AF
.........;
|Text = "Wrong Serial !"
0040440E |. FF75 08 ......PUSH
DWORD PTR SS:[EBP+8] .....;
|hOwner
00404411 |. E8 0A010000 ..CALL <JMP.&USER32.MessageBoxA>
; \MessageBoxA
00404416 |. EB 14 ........JMP
SHORT Keygenm.0040442C
00404418 |> 6A 00 ........PUSH
0 ........................; /Style
= MB_OK|MB_APPLMODAL
0040441A |. 68 A7604000 ..PUSH Keygenm.004060A7
.........; |Title =
"success"
0040441F |. 68 7C604000 ..PUSH Keygenm.0040607C
.........; |Text =
"Good Job, ..."
00404424 |. FF75 08 ......PUSH
DWORD PTR SS:[EBP+8] .... ; |hOwner
00404427 |. E8 F4000000 ..CALL <JMP.&USER32.MessageBoxA>
; \MessageBoxA
Juste au-dessus le
premier call MessageBox le programme effectue un test en 004043FE, on
peut supposer qu'il s'agit d'un test qui permet de diriger vers le bon
message. En effet si on regarde le code, 'test eax, eax' permet de vérifier
si le registre eax est à 0, s'il n'y est pas on saute en 00404418, or
à cette adresse c'est le call qui affiche le test de réussite ! Si on
désirait cracker le prog il faudrait remplacer le JNZ par un JMP qui ferait
sauter le programme quelque soit le numéro de série entré. Donc en 004044A9
on vérifie si le numéro entré est correct... Mais à quoi correspond le
call juste au-dessus ? Eh bien allons voir. On clique à l'adresse 004043F4
et on pose le breakpoint en tapant F2, on relance le prog en faisant Ctrl+F2,
on fait 'Oui', F9, le bouton 'Check' du crackme et nous voilà sur cette
ligne. Pour rentrer dans le call on fait F7, et on arrive ici :
0040444A /$ 6A 32 .
.
..
.PUSH
32 ..........................; /Count
= 32 (50.)
0040444C |. 68 6C614000 .
PUSH
Keygenme.0040616C ...........;
|Buffer = Keygenme.0040616C
00404451 |. FF35 A8684000 PUSH DWORD PTR DS:[4068A8] .......;
|hWnd = 000502BC
00404457 |. E8 AC000000 .
CALL
<JMP.&USER32.GetWindowTextA> ; \GetWindowTextA
On cherche donc ici
à récupérer un des champs rentré dans un buffer (adresse où on stockera
les données récupérées) à l'adresse 0040616C de taille maximale 50 caractères
(32 en hexadécimal). En faisant F8 et en allant voir à l'adresse 0040616C,
on remarque qu'il charge en fait notre nom (pour cela faîtes un dump de
la section .data sous OllyDbg et allez à l'adresse voulue. Ensuite le
programme génère un numéro de série correspondant au nom entré :
0040445C |. 0BC0 .
.
..
.
OR
EAX,EAX
0040445E |. 74 48 .
.
..
.JE
SHORT Keygenme.004044A8
00404460 |. 68 6C614000
.
PUSH
Keygenme.0040616C ............; ASCII
"Le_MaLaDe"
00404465 |. 50
.
.
.
.
.
.PUSH
EAX
00404466 |. 68 6C654000
.
PUSH
Keygenme.0040656C
0040446B |. E8 90CBFFFF
..CALL
Keygenme.00401000
00404470 |. BE 6C654000
..MOV
ESI,Keygenme.0040656C
00404475 |. 33C9 .
.
.
.
.XOR
ECX,ECX
00404477 |. 33C0 .
.
.
.
.XOR
EAX,EAX
00404479 |> 33DB .
.
.
.
.XOR
EBX,EBX
0040447B |. 8A1C31 .
.
.
.MOV
BL,BYTE PTR DS:[ECX+ESI]
0040447E |. 84DB .
.
.
.
.TEST
BL,BL
00404480 |. 74 0E .
.
.
.
JE
SHORT Keygenme.00404490
00404482 |. 0FAFD9 .
.
.
.IMUL
EBX,ECX
00404485 |. 0FAFDB .
.
.
.IMUL
EBX,EBX
00404488 |. 83C3 50 .
.
.
ADD
EBX,50
0040448B |. 03C3 .
.
.
.
.ADD
EAX,EBX
0040448D |. 41 .
.
.
.
.
.INC
ECX
0040448E |.^EB E9 .
.
.
.
JMP
SHORT Keygenme.00404479
00404490 |> 35 78563412 .
XOR
EAX,12345678
00404495 |. 50 PUSH EAX .....................................;
/<%lu>
00404496 |. 68 78604000 .
PUSH
Keygenme.00406078 ............; |Format
= "%lu"
0040449B |. 68 6C674000 .
PUSH Keygenme.0040676C ............; |s=Keygenme.0040676C
004044A0 |. E8 45000000 .
CALL <JMP.&USER32.wsprintfA>
......; \wsprintfA
004044A5 |. 83C4 0C ......ADD
ESP,0C
004044A8 \> C3 ...........RETN
004044DA
|. 47............INC EDI
La première instruction
vérifie si le nombre de caractères lus est 0, si oui on quitte le call,
sinon on continue.
On appelle une fonction avec comme arguments le nom entré, le nombre de
caractères du nom, puis une adresse. Après le call, on place dans le registre
ESI l'adresse utilisée par le call, il s'agissait en fait d'un buffer.
On initialise ECX et EAX. On initialise ensuite EBX à 0, on va boucler
sur la chaîne obtenue précédemment.
On effectue quelques opérations pour chaque caractère lu, puis on formate
la chaîne ainsi obtenue en entier.
On sort du call, vous verrez pourquoi par la suite pourquoi je n'explique
pas la routine.
On rentre dans le call qui suit, qui normalement vérifie le numéro de
série :
004044A9
/$ 6A 32 ........PUSH 32 ..........................;
/Count = 32 (50.)
004044AB |. 68 6C634000 ..PUSH Keygenme.0040636C
...........;
|Buffer = Keygenme.0040636C
004044B0 |. FF35 A4684000 PUSH DWORD PTR DS:[4068A4]........;
|hWnd = 000602C4
004044B6 |. E8 4D000000 ..CALL <JMP.&USER32.GetWindowTextA>
; \GetWindowTextA
004044BB |. 0BC0 .........OR EAX,EAX
004044BD |. 74 29 ........JE SHORT Keygenme.004044E8
004044BF |. 33C9 .........XOR ECX,ECX
004044C1 |. 33D2 .........XOR EDX,EDX
004044C3 |. 33DB .........XOR EBX,EBX
004044C5 |. 33FF .........XOR EDI,EDI
004044C7 |> 8B91 6C634000 MOV EDX,DWORD PTR DS:[ECX+40636C]
004044CD |. 8B99 6C674000 MOV EBX,DWORD PTR DS:[ECX+40676C]
004044D3 |. 33D3 .........XOR
EDX,EBX
004044D5 |. 75 0F ........JNZ
SHORT Keygenme.004044E6
004044D7 |. 83C1 04 ......ADD
ECX,4
004044DA |. 47............INC
EDI
004044DB |. 3BF8 .........CMP
EDI,EAX
004044DD |.^7C E8 ........JL
SHORT Keygenme.004044C7
004044DF |. B8 01000000 ..MOV
EAX,1
004044E4 |. EB 02 ........JMP
SHORT Keygenme.004044E8
004044E6 |> 33C0 .........XOR
EAX,EAX
004044E8 \> C3 ...........RETN
Et là bingo il s'agit
bien de notre routine de vérif', d'ailleurs on peut voir à la fin un 'mov
eax, 1', et un 'xor eax, eax', d'où le 'test eax, eax' juste après !
On parcourt donc le bon numéro de série qui se situe à l'adresse 0040676C
et celui entré qui est à l'adresse 40636C, on prend les 4 premiers chiffres,
on fait un xor sur les 2 séries de 4 chiffres prises, et si le résultat
est 0 c'est-à-dire les 2 séries identiques donc serial valide, on prend
les 4 chiffres suivants, etc...
Si on est arrivé jusqu'au bout de la boucle sans sortir, on met EAX à
1 (numéro de série valide), sinon EAX est à 0
:-((( Ok, on sait donc où se trouve notre numéro de série valide, mais
dans tout ça, comment se génère-t-il ?
Ben c'est là qu'on va se servir du reversing plutôt que de keygener
à proprement dit.
Si on veut observer comment se génère le premier numéro on entre dans
le call 00401000 qui se situe à l'adresse 0040446B,
et là que voit-on ? A peu près 3000 lignes d'assembleur... Alors bon,
j'ai cherché à comprendre au début, mais quand j'ai vu qu'il y avait 3000
lignes comme ça, je me suis dit, bon on verra plus tard pour le keygener,
et là m'est venu une idée, oui oui. On sait donc que le bon numéro de
série se trouve à l'adresse 0040676C, et quand on rentre un numéro de
série au hasard on a toujours le message d'erreur (à moins de tomber par
hasard dessus mais alors là je serais sur le cul franchement !).
Quand on fait notre
appel à MessageBox, on a vu que les messages étaient passé en argument
par un push, et si au lieu d'afficher le message d'erreur on affichait
le numéro de série valide ? Puis ça change de toujours keygener en plus.
Hum, essayons, revenons donc à l'adresse 00404409 :
00404402
|. 6A 00 ........PUSH
0 ........................; /Style=MB_OK|MB_APPLMODAL
00404404 |. 68 BE604000 ..PUSH
Keygenm.004060BE .........;
|Title = "error"
00404409 |. 68 AF604000 ..PUSH
Keygenm.004060AF .........;
|Text = "Wrong Serial !"
0040440E |. FF75 08 ......PUSH
DWORD PTR SS:[EBP+8] .....;
|hOwner
00404411 |. E8 0A010000 ..CALL
<JMP.&USER32.MessageBoxA> ; \MessageBoxA
On doucle-clique sur
'PUSH Keygenm.004060AF' pour pouvoir changer le code assembleur, on le
remplace par 'PUSH 40676C', on clique sur 'Assemble', on continue l'exécution
du programme en faisant F9, on entre notre nom, 'Check', et que voit-on
? Plus de message d'erreur mais le numéro qui apparaît :o)))
On prend donc notre
éditeur hexa, on note la modification à effectuer ici '68AF604000'.
Dans Hex Workshop on fait 'Edit', 'Find...', puis on choisit 'Hex Values',
on entre notre valeur, puis 'OK'.
Quand on a modifié notre code dans OllyDbg, le nouveau code est apparu
en rouge :
00404402
|. 6A 00 ........PUSH
0 ........................; /Style=MB_OK|MB_APPLMODAL
00404404 |. 68 BE604000 ..PUSH
Keygenm.004060BE .........;
|Title = "error"
00404409 |. 68 6C674000 ..PUSH
Keygenm.0040676C .........; |Text
= "Wrong Serial !"
0040440E |. FF75 08 ......PUSH
DWORD PTR SS:[EBP+8] .....;
|hOwner
00404411 |. E8 0A010000 ..CALL
<JMP.&USER32.MessageBoxA> ; \MessageBoxA
On remplace donc les
octets 'AF60' par '6C67', on sauvegarde, et le tour est joué !
On exécute notre programme modifié, on rentre le nom, 'Check', et le message
d'erreur s'affiche avec notre numéro de série correspondant, on le note,
on entre ce numéro, on réessaie, et le bon message s'affiche !
|